fix(msi): allow custom installation directory
authorJyrki Gadinger <nilsding@nilsding.org>
Tue, 15 Apr 2025 09:47:49 +0000 (11:47 +0200)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Tue, 15 Apr 2025 14:36:02 +0000 (14:36 +0000)
Signed-off-by: Jyrki Gadinger <nilsding@nilsding.org>
admin/win/msi/CMakeLists.txt
admin/win/msi/EnsureACL.js [new file with mode: 0644]
admin/win/msi/Nextcloud.wxs

index 933037dac1151ca34f7475c34a85e341e469230a..26385a88b385b4185535869f6f5323892898fc10 100644 (file)
@@ -25,6 +25,7 @@ install(FILES
         ${CMAKE_CURRENT_BINARY_DIR}/OEM.wxi
         ${CMAKE_CURRENT_BINARY_DIR}/collect-transform.xsl
         ${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat
+        EnsureACL.js
         Platform.wxi
         Nextcloud.wxs
         ${CMAKE_CURRENT_BINARY_DIR}/RegistryCleanup.vbs
diff --git a/admin/win/msi/EnsureACL.js b/admin/win/msi/EnsureACL.js
new file mode 100644 (file)
index 0000000..f6929e7
--- /dev/null
@@ -0,0 +1,44 @@
+// writes a message to the MSI logs
+function logInfo(message) {
+    var record = Session.Installer.CreateRecord(0);
+    record.stringData(0) = message
+    // 0x40000000 = msiMessageTypeUser -- see https://learn.microsoft.com/en-gb/windows/win32/msi/session-message#parameters
+    Session.Message(0x04000000, record)
+}
+
+function EnsureACL() {
+    var shell = new ActiveXObject("WScript.Shell");
+    var fs = new ActiveXObject("Scripting.FileSystemObject");
+
+    var programFilesPath = fs.GetAbsolutePathName(shell.ExpandEnvironmentStrings("%PROGRAMFILES%"));
+    var installPath = fs.GetAbsolutePathName(Session.Property("CustomActionData"));
+
+    logInfo("programFilesPath: " + programFilesPath + "\r\n" + "installPath: " + installPath);
+
+    if (installPath.toLowerCase().indexOf(programFilesPath.toLowerCase()) == 0) {
+        // no need to adapt ACLs when installing to C:/Program Files
+        return 0;
+    }
+
+    // using SIDs here (prefixed by *) to avoid potential issues with non-English installs
+    // see also: https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
+    var grants = [
+        "*S-1-5-32-544:(OI)(CI)F",    // DOMAIN_ALIAS_RID_ADMINS    => full access
+        "*S-1-5-18:(OI)(CI)F",        // SECURITY_LOCAL_SYSTEM_RID  => full access
+        "*S-1-5-32-545:(OI)(CI)RX"    // DOMAIN_ALIAS_RID_USERS     => read, execute
+    ];
+    var grantsOptions = "";
+    for (var i = 0; i < grants.length; i++) {
+        grantsOptions += ' /grant "' + grants[i] + '" ';
+    }
+
+    var icaclsCommand = 'icacls.exe "' + installPath + '" /inheritance:r ' + grantsOptions;
+    logInfo("Command: " + icaclsCommand);
+    var retval = shell.Run(icaclsCommand, 0, true);
+    if (retval != 0) {
+        logInfo("Return code: " + retval);
+        return 1603; // fatal error
+    }
+
+    return 0;
+}
index c14cbe030699b7d8c0364793d57efd3a857c9e34..38ea1632a0eb26a51dc4c9a5b78f44ce5139a295 100644 (file)
@@ -57,8 +57,8 @@
     <!-- Detect legacy NSIS installation -->
     <Property Id="NSIS_UNINSTALLEXE">
         <RegistrySearch Id="RegistryLegacyUninstallString" Type="file" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.AppName)" Name="UninstallString" Win64="no">
-                   <FileSearch Id="LegacyUninstallFileName" Name="Uninstall.exe"/>
-           </RegistrySearch>
+            <FileSearch Id="LegacyUninstallFileName" Name="Uninstall.exe"/>
+        </RegistrySearch>
     </Property>
 
     <!-- Property to disable update checks -->
     <SetProperty Id="ExecNsisUninstaller" Value="&quot;$(var.AppShortName)&quot; &quot;[NSIS_UNINSTALLEXE]&quot;" Before="ExecNsisUninstaller" Sequence="execute" />
     <SetProperty Id="RemoveNavigationPaneEntries" Value="&quot;$(var.AppName)&quot;" Before="RemoveNavigationPaneEntries" Sequence="execute" />
 
+    <!-- Ensure ACLs -->
+    <Binary Id="EnsureACL" SourceFile="EnsureACL.js" />
+    <SetProperty Id="EnsureACL" Before="EnsureACL" Value="[INSTALLDIR]" Sequence="execute" />
+    <CustomAction Id="EnsureACL" BinaryKey="EnsureACL" JScriptCall="EnsureACL" Return="check" Execute="deferred" Impersonate="no" />
+
     <InstallExecuteSequence>
         <!-- Install: Remove previous NSIS installation, if detected -->
         <Custom Action="ExecNsisUninstaller" Before="ProcessComponents">NSIS_UNINSTALLEXE AND NOT Installed</Custom>
 
+        <!-- Install: Ensure ACLs are set -->
+        <Custom Action="EnsureACL" After="InstallFiles">NOT Installed</Custom>
+
         <!-- Uninstall: Remove sync folders from Explorer's Navigation Pane, only effective for the current user (home users) -->
         <Custom Action="RemoveNavigationPaneEntries" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
-               
-               <!-- Uninstall: Cleanup the Registry -->
-               <Custom Action="RegistryCleanupCustomAction" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
+
+        <!-- Uninstall: Cleanup the Registry -->
+        <Custom Action="RegistryCleanupCustomAction" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
 
         <!-- Schedule Reboot for the Shell Extensions (in silent installation mode only, or if SCHEDULE_REBOOT argument is set-->
         <ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR NOT (UILevel=2)</ScheduleReboot>
     </InstallExecuteSequence>
 
     <!-- "Add or Remove" Programs Entries -->
-       <Property Id="APPNAME">$(var.AppName)</Property>
+    <Property Id="APPNAME">$(var.AppName)</Property>
     <Property Id="ARPPRODUCTICON">$(var.AppIcon)</Property>
     <Property Id="ARPHELPLINK">$(var.AppHelpLink)</Property>
     <Property Id="ARPURLINFOABOUT">$(var.AppInfoLink)</Property>
                 <RegistryValue Type="integer" Name="skipUpdateCheck" Value="[SKIPAUTOUPDATE]" />
             </RegistryKey>
         </Component>
-               <!-- Register URI handler -->
+        <!-- Register URI handler -->
         <Component Id="RegistryUriHandler" Guid="*" Win64="$(var.PlatformWin64)">
             <RegistryKey Root="HKLM" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
                 <RegistryValue Type="string" Value="URL:$(var.AppName) Protocol" />
-                               <RegistryValue Type="string" Name="URL Protocol" Value="" />
+                <RegistryValue Type="string" Name="URL Protocol" Value="" />
             </RegistryKey>
             <RegistryKey Root="HKLM" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)\DefaultIcon" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
                 <RegistryValue Type="string" Value="[INSTALLDIR]$(var.AppExe)" />
 
         <ComponentRef Id="RegistryVersionInfo" />
         <ComponentRef Id="RegistryDefaultSettings" />
-               <ComponentRef Id="RegistryUriHandler" />
+        <ComponentRef Id="RegistryUriHandler" />
 
         <Feature Id="ShellExtensions" Title="Integration for Windows Explorer"
             Description="This feature requires a reboot." >